

local inspect = require "ddcl.inspect"
local tcp_channel = require "ddcl.tcp_channel"
local socket = require "ddcl.socket"
local service = require "ddcl.service"
local sha1 = require "ddcl.sha1"
local base64 = require "ddcl.base64"
local utils = require "ddcl.websocket.utils"
local http_utils = require "ddcl.http.utils"

local S_READY = 0
local S_CONNECTING = 1
local S_CONNECTED = 2

local function sec_key()
    local r = tostring(math.random(42131, 9312441))
    return base64.encode(sha1.encode(r))
end


local function read_response(M)
    local t = http_utils.read_response_header(M.channel, M.max_header_len)
    assert(t.code == 101, t.code)
    local header = t.header
    local content_len = header["content-length"]
    if content_len then
        content_len = tonumber(content_len)
        t.body = channel.read(content_len)
    end
    local upgrade = header["upgrade"]
    if not upgrade or string.lower(upgrade) ~= "websocket" then
        error("server not support websocket")
    end
    return t
end

local function do_connect(M)
    M.status = S_CONNECTING
    local fd = socket.connect(M.host, M.port)
    assert(fd, "connect error")
    M.channel = tcp_channel(fd, {
        forward = M.forward,
        close_fn = function()
            M.channel = nil
            if M.close_fn then M.close_fn(M) end
        end
    })

    local head = {
        string.format("GET ws://%s:%d/ HTTP/1.1", M.host, M.port),
        "Connection: Upgrade",
        "Sec-WebSocket-Extensions: x-webkit-deflate-frame",
        string.format("Sec-WebSocket-Key: %s", sec_key()),
        "Sec-WebSocket-Version: 13",
        "Upgrade: WebSocket",
        "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits",
        "\r\n"
    }
    local str = table.concat(head, "\r\n")
    M.channel.send(str)
    M.info = read_response(M)
    M.status = S_CONNECTED

    service.fork(function()
        while M.channel do
            local ret, data = pcall(utils.read_packet, M.channel)
            assert(ret, data)
            if not data then M.close() break end
            if M.message_fn then
                M.message_fn(M, data)
            end
        end
    end)
end

local function do_send(M, data, is_text)
    local packet = utils.make_packet(data, true, is_text)
    M.channel.send(packet)
end

local MT = {
    __gc = function(self)
        self.close()
    end,
}
MT.__index = MT
local function create(opt)
    local M = {
        host = opt.host,
        port = opt.port,
        forward = opt.forward,
        close_fn = opt.close_fn,
        message_fn = opt.message_fn,
        status = S_READY,
    }

    function M.connect()
        if M.status ~= S_READY then
            return
        end
        return do_connect(M)
    end

    function M.send(data)
        if M.status ~= S_CONNECTED then
            return
        end
        do_send(M, data)
    end

    function M.close()
        if M.status == S_CONNECTED and M.channel then
            M.channel.close()
            M.channel = nil
        end
    end

    return setmetatable(M, MT)
end


return create
